package com.inwavetech.os.util;

import android.content.res.Resources;
import android.text.Html;
import android.util.Log;
import android.widget.Toast;

import com.inwavetech.os.R;
import com.inwavetech.os.entity.Os;
import com.inwavetech.os.entity.Product;
import com.inwavetech.os.entity.User;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.misc.TransactionManager;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.math.BigDecimal;
import java.sql.SQLException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Callable;

/**
 * Created by jgskin on 2/11/14.
 */
public class OsJsonParser {

    private static final EnumHandler osStausHandler;
    private static final EnumHandler osClosingStausHandler;
    private static final EnumHandler productDiagnosysHandler;

    private static final SimpleDateFormat dateFormatter;

    private static final EnumHandler serviceOrderTypeHandler;

    static {
        HashMap<Integer, Enum> osStatusConfig = new HashMap<Integer, Enum>();
        osStatusConfig.put(1, Os.Status.OPEN);
        osStatusConfig.put(2, Os.Status.ACTIVE);
        osStatusConfig.put(3, Os.Status.FINISHED);

        osStausHandler = new EnumHandler(osStatusConfig);

        HashMap<Integer, Enum> osClosingStatusConfig = new HashMap<Integer, Enum>();
        osClosingStatusConfig.put(1, Os.ClosingStatus.CONCLUDED);
        osClosingStatusConfig.put(2, Os.ClosingStatus.ON_HOLD);
        osClosingStatusConfig.put(3, Os.ClosingStatus.CANCELED);

        osClosingStausHandler = new EnumHandler(osClosingStatusConfig);

        HashMap<Integer, Enum> productDiagnosysConfig = new HashMap<Integer, Enum>();
        productDiagnosysConfig.put(1, Product.Diagnosys.ADJUST);
        productDiagnosysConfig.put(2, Product.Diagnosys.BOARD_CHANGE);
        productDiagnosysConfig.put(3, Product.Diagnosys.BOARD_CHANGE_NEEDED);
        productDiagnosysConfig.put(4, Product.Diagnosys.POWER_CHANGE);
        productDiagnosysConfig.put(5, Product.Diagnosys.POWER_CHANGE_NEEDED);
        productDiagnosysConfig.put(6, Product.Diagnosys.PRODUCT_CHANGE);
        productDiagnosysConfig.put(7, Product.Diagnosys.PRODUCT_CHANGE_NEEDED);
        productDiagnosysConfig.put(8, Product.Diagnosys.TAG_NEAR);
        productDiagnosysConfig.put(9, Product.Diagnosys.FIXATION);
        productDiagnosysConfig.put(10, Product.Diagnosys.INFRA);
        productDiagnosysConfig.put(11, Product.Diagnosys.RESET);
        productDiagnosysConfig.put(12, Product.Diagnosys.COMPLEX);

        productDiagnosysHandler = new EnumHandler(productDiagnosysConfig);

        HashMap<Integer, Enum> serviceOrderTypeConfig = new HashMap<Integer, Enum>();
        serviceOrderTypeConfig.put(1, Os.ServiceOrderType.INSTALL);
        serviceOrderTypeConfig.put(2, Os.ServiceOrderType.MAINTENANCE);
        serviceOrderTypeHandler = new EnumHandler(serviceOrderTypeConfig);

        dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz");
    }

    private final Dao<Os, ?> osDao;
    private final Dao<Product, ?> productDao;
    private final Resources resources;
    private final User osOwner;

    public OsJsonParser(Dao osDao, Dao productDao, Resources resources, User osOwner) {
        this.osDao = osDao;
        this.productDao = productDao;
        this.resources = resources;
        this.osOwner = osOwner;
    }

    public List<Os> parse(String jsonString) throws Exception {
        // the os's imported to database
        List<Os> importedOses = new ArrayList<Os>();

        try {
            JSONArray jOses = new JSONArray(jsonString);

            for (int i=0; i < jOses.length(); i++) {
                    // parse json os
                    final JSONObject jOs = jOses.getJSONObject(i);

                    // check if it was already downloaded
                    Map<String, Object> params = new HashMap<String, Object>();
                    params.put("user_id", osOwner.username);
                    params.put("original_id", jOs.getString("id"));
                    List<Os> result = osDao.queryForFieldValues(params);

                    if (result.size() == 0) {

                        try {
                            // import the os atomicly (db transaction)
                            Os os = (Os) TransactionManager.callInTransaction(osDao.getConnectionSource(), new Callable<Object>() {
                                @Override
                                public Os call() throws java.lang.Exception  {
                                    Os os = null;

                                    try {
                                        // parse the os
                                        os = parseOs(jOs);
                                    } catch (JSONException e) {
                                        // missing json entry
                                        logAndRetrow(e);
                                    } catch (ParseException e) {
                                        // could not parse a json entry
                                        logAndRetrow(e);
                                    } catch (SQLException e) {
                                        // could not insert or update the database
                                        logAndRetrow(e);
                                    }

                                    return os;
                                }
                            });

                            if (os != null) {
                                importedOses.add(os);
                            }

                        } catch (SQLException e) {
                            // transaction failure
                        }

                    }

            }

        } catch (JSONException e) {
            logException(e);

            throw new Exception(resources.getString(R.string.bad_response_server_no_sync));
        } catch (SQLException e) {
            logException(e);

            throw new Exception(resources.getString(R.string.database_error_no_sync));
        }

        return importedOses;
    }

    private void logAndRetrow(java.lang.Exception e) throws java.lang.Exception {
        logException(e);

        throw e;
    }

    private Os parseOs(JSONObject jOs) throws ParseException, SQLException, JSONException {
        // create the os entity
        Os os = new Os();
        os.original_id = jOs.getString("id");

        // parse string values
        os.client = parseStringValue(jOs.optString("client"));
        os.store = parseStringValue(jOs.optString("store"));
        os.opener = parseStringValue(jOs.optString("opener"));
        os.origin = parseStringValue(jOs.optString("origin"));
        os.contact = parseStringValue(jOs.optString("contact"));
        os.authorization = parseStringValue(jOs.optString("authorization"));
        os.activated_user = parseStringValue(jOs.optString("activated_user"));
        os.finished_user = parseStringValue(jOs.optString("finished_user"));
        os.assignment = parseStringValue(jOs.optString("assignment"));
        os.assignment_time = parseStringValue(jOs.optString("assignment_time"));
        os.roundup = parseStringValue(jOs.optString("roundup"));
        os.future_observation = parseStringValue(jOs.optString("future_observation"));
        os.maintenance_observation = parseStringValue(jOs.optString("maintenance_observation"));

        os.service_justification = parseStringValue(jOs.getString("service_justification"));
        os.travel_justification = parseStringValue(jOs.getString("travel_justification"));
        os.roundup = parseStringValue(jOs.getString("roundup"));
        os.future_observation = parseStringValue(jOs.getString("future_observation"));
        os.name_responsible = parseStringValue(jOs.getString("name_responsible"));

        // bigdecimal
        os.travel_price = new BigDecimal(jOs.getDouble("travel_price"));
        os.service_price = new BigDecimal(jOs.getDouble("service_price"));

        // enum values
        os.service_order_type = (Os.ServiceOrderType) serviceOrderTypeHandler.getEnum(jOs.getInt("service_order_type_id"));
        os.status = (Os.Status) osStausHandler.getEnum(jOs.getInt("status_id"));
        os.closing_status = (Os.ClosingStatus) osClosingStausHandler.getEnum(jOs.getInt("closing_status_id"));

        // parse date values
        os.created_at = dateFormatter.parse(jOs.getString("created_at"));
        os.activated_date = dateFormatter.parse(jOs.getString("activated_at"));
        os.assignment_date = dateFormatter.parse(jOs.optString("assignment_date"));
        os.finished_at = dateFormatter.parse(jOs.optString("finished_at"));

        // set the owner
        os.user = osOwner;

        osDao.create(os);

        // create products entity
        JSONArray jProducts = jOs.getJSONArray("products");
        for (int j=0; j < jProducts.length(); j++) {
            JSONObject jProduct = jProducts.getJSONObject(j);

            // create the product
            Product product = new Product();

            // parse string values
            product.original_id = parseStringValue(jProduct.getString("id"));
            product.name = parseStringValue(jProduct.optString("name"));
            product.identification = parseStringValue(jProduct.optString("identification"));
            product.factory_warranty = parseStringValue(jProduct.optString("factory_warranty"));
            product.service_warranty = parseStringValue(jProduct.optString("service_warranty"));
            product.document_code = parseStringValue(jProduct.optString("document_code"));
            product.maintenance_type = parseStringValue(jProduct.optString("maintenance_type"));
            product.hired_services = parseStringValue(jProduct.optString("hired_services"));
            product.maintenance_detail = parseStringValue(jProduct.optString("maintenance_detail"));
            product.setup_detail = parseStringValue(jProduct.optString("setup_detail"));
            product.diagnosys_detail = parseStringValue(jProduct.optString("diagnostic_details"));

            //parse enum values
            product.diagnosys = (Product.Diagnosys) productDiagnosysHandler.getEnum(jProduct.getInt("diagnostic_id"));

            // parse date values
            product.document_date = dateFormatter.parse(jProduct.optString("document_date"));

            // link the os
            product.os = os;

            productDao.create(product);
        }

        return os;
    }

    private void logException(java.lang.Exception e) {
        Log.e(OsJsonParser.class.getName(), e.getMessage());
    }

    private String parseStringValue(String value) {
        return Html.fromHtml(value).toString();
    }

    public class Exception extends java.lang.Exception {
        public Exception(String s) {
            super(s);
        }
    }
}
